﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Threading;
using System.Windows.Forms;
using UGSV_GUI.Properties;


namespace GMapNET
{
   public partial class GMapControl : UserControl
   {
      #region -- fields --

      /// <summary>
      /// google maps manager
      /// </summary>
      public readonly static GoogleMaps Manager = new GoogleMaps();

      PointLatLng currentPosition;
      Point currentPositionTile;
      Point currentPositionPixel;

      Point renderOffset;
      Point centerTileXYLocation;
      Point centerTileXYLocationLast;
      Point centerTileXYOffset;
      Point dragPoint;

      Point mouseDown;
      Point mouseCurrent;

      // testing
      Point mouseDownToolTip;
      Point mouseCurrentToolTip;
      Marker currentlyDragged;       

      Size sizeOfMapArea;
      Size sizeOfTiles;

      Rectangle tileRect = new Rectangle(new Point(0, 0), GoogleMaps.TileXY);
      Point tilePoint;

      TileMatrix matrix = new TileMatrix();
      BackgroundWorker boundsChecker = new BackgroundWorker();
      BackgroundWorker loader = new BackgroundWorker();
      BackgroundWorker loader2 = new BackgroundWorker();
      BackgroundWorker loader3 = new BackgroundWorker();
      EventWaitHandle waitOnEmptyTasks = new AutoResetEvent(false);
      EventWaitHandle waitForBoundsChanged = new AutoResetEvent(true);
      List<Point> tileDrawingList = new List<Point>();
      Queue<Point> tileLoadQueue = new Queue<Point>();
      List<Route> routes = new List<Route>();
      List<Marker> markers = new List<Marker>();

      readonly Pen routePen = new Pen(Color.MidnightBlue);
      readonly Pen tooltipPen = new Pen(Color.FromArgb(140, Color.Blue));
      readonly Color tooltipBg = Color.FromArgb(255, Color.Gray);
      StringFormat tooltipFormat = new StringFormat();

      /// <summary>
      /// font for tooltips
      /// </summary>
      public Font TooltipFont = new Font(FontFamily.GenericSansSerif, 11, FontStyle.Regular);
      
      /// <summary>
      /// for tooltip text padding
      /// </summary>
      public Size TooltipTextPadding = new Size(10, 10);

      //readonly Font txtFont = new Font(FontFamily.GenericSansSerif, 14, FontStyle.Bold);
      readonly int SourceCopy = (int) CopyPixelOperation.SourceCopy;
      readonly Font gFont = new Font(FontFamily.GenericSansSerif, 7, FontStyle.Regular);
      string copyright = string.Format("©{0} Google - Map data ©{0} Tele Atlas", DateTime.Today.Year);
      SizeF copyrightSizeF = SizeF.Empty;

      IntPtr hdcTmp, hdcMemTmp;
      bool dragging;
      bool mouseVisible = true;
      int zoom = 12;
      bool currentMarkerEnabled = true;
      bool isMouseOverMarker;

     
      readonly List<double> Uu = new List<double>();
      readonly List<double> Vu = new List<double>();
      readonly List<double> Ru = new List<double>();
      readonly List<double> Tu = new List<double>();

      /// <summary>
      /// type of map
      /// </summary>
      public GMapType MapType = GMapType.VirtualEarthMap;

      /// <summary>
      /// language for map
      /// </summary>
      public string Language = "en";

      /// <summary>
      /// is map ussing cache for tiles
      /// </summary>
      public bool UseTileCache = true;

      /// <summary>
      /// is map ussing cache for routing
      /// </summary>
      public bool UseRouteCache = true;

      /// <summary>
      /// is map using cache for geocoder
      /// </summary>
      public bool UseGeocoderCache = true;

      /// <summary>
      /// is map using cache for placemarks
      /// </summary>
      public bool UsePlacemarkCache = true;

      /// <summary>
      /// is routes enabled
      /// </summary>
      public bool RoutesEnabled = true;

      /// <summary>
      /// is markers enabled
      /// </summary>
      public bool MarkersEnabled = true;

      /// <summary>
      /// can user drag map
      /// </summary>
      public bool CanDragMap = true;

      /// <summary>
      /// style of the current marker
      /// </summary>
      public CurrentMarkerType CurrentMarkerStyle = CurrentMarkerType.GMap;

      /// <summary>
      /// map render mode
      /// </summary>
      public RenderMode RenderMode = RenderMode.GDI;

      /// <summary>
      /// occurs when current position is changed
      /// </summary>
      public event CurrentPositionChanged OnCurrentPositionChanged;
      public delegate void CurrentPositionChanged(PointLatLng point);

      /// <summary>
      /// occurs when marker position is updated
      /// </summary>
      public event MarkerPositionUpdated OnMakerPositionUpdated;
      public delegate void MarkerPositionUpdated(Marker m);

      /// <summary>
      /// occurs when painting is complete
      /// </summary>
      public event PaintComplete OnPaintComplete;
      public delegate void PaintComplete(Graphics g);

      /// <summary>
      /// occurs when tile set load is complete
      /// </summary>
      public event TileLoadComplete OnTileLoadComplete;
      public delegate void TileLoadComplete(int loaderId);

      /// <summary>
      /// occurs when tile set is starting to load
      /// </summary>
      public event TileLoadStart OnTileLoadStart;
      public delegate void TileLoadStart(int loaderId);

      /// <summary>
      /// occurs when clicked on marker
      /// </summary>
      public event MarkerClick OnMarkerClick;
      public delegate void MarkerClick(List<Marker> items);

      /// <summary>
      /// occurs on mouse enters marker area
      /// </summary>
      public event MarkerEnter OnMarkerEnter;
      public delegate void MarkerEnter(Marker item);

      /// <summary>
      /// occurs on mouse leaves marker area
      /// </summary>
      public event MarkerLeave OnMarkerLeave;
      public delegate void MarkerLeave(Marker item);

      #endregion

      #region -- properties --

      /// <summary>
      /// current marker position
      /// </summary>
      public PointLatLng CurrentPosition
      {
         get
         {
            return currentPosition;
         }
         set
         {
            currentPosition = value;
            currentPositionPixel = GMapControl.Manager.FromLatLngToPixel(value, Zoom);
            currentPositionTile = GMapControl.Manager.FromPixelToTileXY(currentPositionPixel);

            if(OnCurrentPositionChanged != null)
               OnCurrentPositionChanged(currentPosition);
         }
      }

      /// <summary>
      /// current marker position in pixel coordinates
      /// </summary>
      public Point CurrentPositionGPixel
      {
         get
         {
            return currentPositionPixel;
         }
         internal set
         {
            currentPositionPixel = value;
         }
      }

      /// <summary>
      /// google tile in which current marker is
      /// </summary>
      public Point CurrentPositionGTile
      {
         get
         {
            return currentPositionTile;
         }
         internal set
         {
            currentPositionTile = value;
         }
      }

      /// <summary>
      /// location of the cache
      /// </summary>
      [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
      public string Cache
      {
         get
         {
            return Manager.Cache.CacheLocation;
         }
         set
         {
            Manager.Cache.CacheLocation = value;
         }
      }

      /// <summary>
      /// total count of google tiles at current zoom
      /// </summary>
      public long TotalTiles
      {
         get
         {
            return GoogleMaps.GetTotalTileCount(Zoom);
         }
      }

      /// <summary>
      /// current zoom value
      /// </summary>
      [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
      public int Zoom
      {
         get
         {
            return zoom;
         }
         set
         {
            zoom = value;
            sizeOfTiles = GoogleMaps.GetTotalTilesXY(value);
            currentPositionPixel = GMapControl.Manager.FromLatLngToPixel(currentPosition, value);
            currentPositionTile = GMapControl.Manager.FromPixelToTileXY(currentPositionPixel);
         }
      }

      /// <summary>
      /// is now map dragging
      /// </summary>
      public bool IsDragging
      {
         get
         {
            return dragging;
         }
      }

      /*
      /// <summary>
      /// is now user dragging ToolTip
      /// </summary>
      public bool IsDraggingToolTip
      {
         get
         {
            return (currentlyDragged != null);
         }
      }
      */

      /// <summary>
      /// is mouse over marker
      /// </summary>
      public bool IsMouseOverMarker
      {
         get
         {
            return isMouseOverMarker;
         }
      }

      /// <summary>
      /// is current marker enabled and visible
      /// </summary>
      public bool CurrentMarkerEnabled
      {
         get
         {
            return currentMarkerEnabled;
         }
         set
         {
            currentMarkerEnabled = value;
            SafeInvalidate();
         }
      }

      /// <summary>
      /// gets current map view top/left coordinate, width in Lng, height in Lat
      /// </summary>
      /// <returns></returns>
      public RectLatLng CurrentViewArea
      {
         get
         {
            RectLatLng ret = new RectLatLng();

            ret.Location = Manager.FromPixelToLatLng(-renderOffset.X, -renderOffset.Y, Zoom);

            double lng = Manager.FromPixelToLatLng(-renderOffset.X + Width, -renderOffset.Y, Zoom).Lng - ret.Location.Lng;
            double lat = Manager.FromPixelToLatLng(-renderOffset.X, -renderOffset.Y - Height, Zoom).Lat - ret.Location.Lat;
            ret.Size = new SizeLatLng(lng, lat);

            return ret;
         }
      }

      #endregion

      public void SetCache(string mapName)
      {
          Cache = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + "TracerLinkTAV" + Path.DirectorySeparatorChar + "Map" + Path.DirectorySeparatorChar + mapName + Path.DirectorySeparatorChar;
          Manager.Cache.CheckCacheSize();
          switch (mapName)
          {
              case "VirtualEarthMap":
                  copyright = string.Format("©{0} Microsoft Corporation - Map data ©{0} NAVTEQ", DateTime.Today.Year);
                  break;
              case "VirtualEarthHybrid":
                  copyright = string.Format("©{0} Microsoft Corporation - Map data ©{0} NAVTEQ", DateTime.Today.Year);
                  break;
              case "YahooSatellite":
                  copyright = string.Format("©{0} Yahoo! - Map data ©{0} NAVTEQ", DateTime.Today.Year);
                  break;
              case "GoogleSatellite":
                  copyright = string.Format("©{0} Google - Map data ©{0} Tele Atlas", DateTime.Today.Year);
                  break;
          }
          
      }

      #region -- construct --
      public GMapControl()
      {
         InitializeComponent();

         if(!DesignMode)
         {
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.Opaque, true);


            string oldCache = System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData) + Path.DirectorySeparatorChar + "Google" + Path.DirectorySeparatorChar + "GMap.NET" + Path.DirectorySeparatorChar;
            if (Directory.Exists(oldCache))
            {
                Directory.Delete(oldCache, true);
            }
            
             dragging = false;

            this.Region = new Region(new Rectangle(-50, -50, Size.Width+100, Size.Height+100));

            routePen.LineJoin = LineJoin.Round;
            routePen.Width = 5;

            tooltipPen.Width = 2;
            tooltipPen.LineJoin = LineJoin.Round;
            tooltipPen.StartCap = LineCap.RoundAnchor;

            tooltipFormat.Alignment     = StringAlignment.Center;
            tooltipFormat.LineAlignment = StringAlignment.Center;

            loader.WorkerReportsProgress = true;
            loader.WorkerSupportsCancellation = true;
            loader.ProgressChanged += new ProgressChangedEventHandler(loader_ProgressChanged);
            loader.DoWork += new DoWorkEventHandler(loader_DoWork);

            loader2.WorkerReportsProgress = true;
            loader2.WorkerSupportsCancellation = true;
            loader2.ProgressChanged += new ProgressChangedEventHandler(loader_ProgressChanged);
            loader2.DoWork += new DoWorkEventHandler(loader2_DoWork);

            loader3.WorkerReportsProgress = true;
            loader3.WorkerSupportsCancellation = true;
            loader3.ProgressChanged += new ProgressChangedEventHandler(loader_ProgressChanged);
            loader3.DoWork += new DoWorkEventHandler(loader3_DoWork);

            boundsChecker.WorkerReportsProgress = true;
            boundsChecker.WorkerSupportsCancellation = true;
            boundsChecker.ProgressChanged += new ProgressChangedEventHandler(loader_ProgressChanged);
            boundsChecker.DoWork += new DoWorkEventHandler(boundsChecker_DoWork);
         }
      }

      ~GMapControl()
      {
         CancelAsyncTasks();
         waitOnEmptyTasks.Close();
      }
      #endregion

      #region -- public --

      /// <summary>
      /// reloads map
      /// </summary>
      public void ReloadMap()
      {
         lock(tileLoadQueue)
         {
            tileLoadQueue.Clear();
         }

         {
            matrix.Clear();

            SafeInvalidate();

            GC.Collect(GC.MaxGeneration);
            GC.WaitForPendingFinalizers();
         }

         GoToCurrentPosition();

         // start loading
         RunAsyncTasks();
      }

      /// <summary>
      /// moves current position into map center
      /// </summary>
      public void GoToCurrentPosition()
      {
         // reset stuff
         renderOffset = Point.Empty;
         centerTileXYOffset = Point.Empty;
         centerTileXYLocationLast = Point.Empty;
         dragPoint = Point.Empty;

         // goto location
         this.Drag(new Point(-(CurrentPositionGPixel.X - Width/2), -(CurrentPositionGPixel.Y - Height/2)));
      }

      /// <summary>
      /// sets to max zoom to fit all markers and centers them in map
      /// </summary>
      public bool ZoomAndCenterMarkers()
      {
         RectLatLng rect = GetRectOfAllMarkers();
         if(rect != RectLatLng.Empty)
         {
            int maxZoom = GetMaxZoomToFitRect(rect);
            if(maxZoom > 0)
            {
               PointLatLng center = new PointLatLng(rect.Lat-(rect.HeightLat/2), rect.Lng+(rect.WidthLng/2));
               CurrentPosition = center;

               if(Zoom != maxZoom)
               {
                  Zoom = maxZoom;
                  ReloadMap();
               }
               else
               {
                  GoToCurrentPosition();
               }
               return true;
            }
         }

         return false;
      }

      /// <summary>
      /// sets current position by geocoding
      /// </summary>
      /// <param name="keys"></param>
      /// <returns></returns>
      public bool SetCurrentPositionByKeywords(string keys)
      {
         PointLatLng? pos = GetLatLngFromGeocoder(keys);
         if(pos.HasValue)
         {
            CurrentPosition = pos.Value;
         }

         return pos.HasValue;
      }

      /// <summary>
      /// gets lat/lng from local control coordinates
      /// </summary>
      /// <param name="local"></param>
      /// <returns></returns>
      public PointLatLng FromLocalToLatLng(Point local)
      {
         return Manager.FromPixelToLatLng(new Point(local.X - renderOffset.X, local.Y - renderOffset.Y), Zoom);
      }

      public Point FromLatLonToPoint(PointLatLng ll)
      {
          Point p = Manager.FromLatLngToPixel(ll, Zoom);
          p.Offset(renderOffset);
          return p;
      }

      /// <summary>
      /// gets rectangle with all markers inside
      /// </summary>
      /// <returns></returns>
      public RectLatLng GetRectOfAllMarkers()
      {
         RectLatLng ret = RectLatLng.Empty;

         lock(markers)
         {
            if(markers.Count > 0)
            {
               double left = double.MaxValue;
               double top = double.MinValue;
               double right = double.MinValue;
               double bottom = double.MaxValue;

               foreach(Marker m in markers)
               {
                  // left
                  if(m.Position.Lng < left)
                  {
                     left = m.Position.Lng;
                  }

                  // top
                  if(m.Position.Lat > top)
                  {
                     top = m.Position.Lat;
                  }

                  // right
                  if(m.Position.Lng > right)
                  {
                     right = m.Position.Lng;
                  }

                  // bottom
                  if(m.Position.Lat < bottom)
                  {
                     bottom = m.Position.Lat;
                  }
               }

               ret = RectLatLng.FromLTRB(left, top, right, bottom);
            }
         }

         return ret;
      }

      /// <summary>
      /// get route between two points
      /// </summary>
      /// <param name="start"></param>
      /// <param name="end"></param>
      /// <param name="language"></param>
      /// <returns></returns>
      public List<PointLatLng> GetRouteBetweenPoints(PointLatLng start, PointLatLng end, bool avoidHighways)
      {
         return Manager.GetRouteBetweenPointsUrl(Manager.MakeRouteUrl(start, end, Language, avoidHighways), Zoom, UseRouteCache);
      }

      /// <summary>
      /// get route between two points
      /// </summary>
      /// <param name="start"></param>
      /// <param name="end"></param>
      /// <param name="language"></param>
      /// <returns></returns>
      public List<PointLatLng> GetRouteBetweenPoints(string start, string end, bool useHighway)
      {
         return Manager.GetRouteBetweenPointsUrl(Manager.MakeRouteUrl(start, end, Language, useHighway), Zoom, UseRouteCache);
      }

      /// <summary>
      /// gets lat, lng from geocoder keys
      /// </summary>
      /// <param name="keywords"></param>
      /// <returns></returns>
      public PointLatLng? GetLatLngFromGeocoder(string keywords)
      {
         return Manager.GetLatLngFromGeocoderUrl(Manager.MakeGeocoderUrl(keywords), UseGeocoderCache);
      }

      /// <summary>
      /// gets placemark from location
      /// </summary>
      /// <param name="location"></param>
      /// <returns></returns>
      public Placemark GetPlacemarkFromGeocoder(PointLatLng location)
      {
         return Manager.GetPlacemarkFromReverseGeocoderUrl(Manager.MakeReverseGeocoderUrl(location, Language), UsePlacemarkCache);
      }

      /// <summary>
      /// adds route
      /// </summary>
      /// <param name="item"></param>
      public void AddRoute(Route item)
      {
         lock(routes)
         {
            routes.Add(item);
         }

         SafeInvalidate();
      }

      /// <summary>
      /// removes route
      /// </summary>
      /// <param name="item"></param>
      public void RemoveRoute(Route item)
      {
         lock(routes)
         {
            routes.Remove(item);
         }

         SafeInvalidate();
      }

      /// <summary>
      /// clears all routes
      /// </summary>
      /// <param name="item"></param>
      public void ClearAllRoutes()
      {
         lock(routes)
         {
            routes.Clear();
         }

         SafeInvalidate();
      }

      /// <summary>
      /// adds marker
      /// </summary>
      /// <param name="item"></param>
      public void AddMarker(Marker item)
      {
         lock(markers)
         {
            markers.Add(item);
         }

         SafeInvalidate();
      }

      /// <summary>
      /// removes marker
      /// </summary>
      /// <param name="item"></param>
      public void RemoveMarker(Marker item)
      {
         lock(markers)
         {
            markers.Remove(item);
         }

         SafeInvalidate();
      }

      /// <summary>
      /// clears all markers
      /// </summary>
      /// <param name="item"></param>
      public void ClearAllMarkers()
      {
         lock(markers)
         {
            markers.Clear();
         }

         SafeInvalidate();
      }

      /// <summary>
      /// manages currently markers on map visibility
      /// </summary>
      /// <param name="visible"></param>
      public void SetCurrentMarkersVisibility(bool visible)
      {
         lock(markers)
         {
            foreach(Marker m in markers)
            {
               m.Visible = visible;
            }
         }

         SafeInvalidate();
      }

      /// <summary>
      /// manages currently markers on map tooltip mode
      /// </summary>
      /// <param name="mode"></param>
      public void SetCurrentMarkersTooltipMode(MarkerTooltipMode mode)
      {
         lock(markers)
         {
            foreach(Marker m in markers)
            {
               m.TooltipMode = mode;
            }
         }

         SafeInvalidate();
      }

      #endregion

      #region -- private --

      /// <summary>
      /// changes current position without changing current gtile
      /// </summary>
      /// <param name="localPoint"></param>
      void ChangeCurrentPositionOnly(Point localPoint)
      {
         currentPosition = Manager.FromPixelToLatLng(localPoint, Zoom);
         currentPositionPixel = GMapControl.Manager.FromLatLngToPixel(currentPosition, Zoom);

         if(OnCurrentPositionChanged != null)
            OnCurrentPositionChanged(currentPosition);
      }

      /// <summary>
      /// thread safe invalidation
      /// </summary>
      void SafeInvalidate()
      {
         if(this.InvokeRequired)
         {
            MethodInvoker m = delegate
            {
               Invalidate(false);
            };
            this.Invoke(m);
         }
         else
         {
            Invalidate(false);
         }
      }

      /// <summary>
      /// cancels tile loaders and bounds checker
      /// </summary>
      void CancelAsyncTasks()
      {
         if(boundsChecker.IsBusy)
         {
            boundsChecker.CancelAsync();
         }

         if(loader.IsBusy)
         {
            loader.CancelAsync();
         }

         if(loader2.IsBusy)
         {
            loader2.CancelAsync();
         }

         if(loader3.IsBusy)
         {
            loader3.CancelAsync();
         }
      }

      /// <summary>
      /// runs tile loaders and bounds checker
      /// </summary>
      void RunAsyncTasks()
      {
         if(!boundsChecker.IsBusy)
         {
            boundsChecker.RunWorkerAsync();
         }

         if(!loader.IsBusy)
         {
            loader.RunWorkerAsync();
         }

         if(!loader2.IsBusy)
         {
            loader2.RunWorkerAsync();
         }

         if(!loader3.IsBusy)
         {
            loader3.RunWorkerAsync();
         }

         waitForBoundsChanged.Set();
      }

      /// <summary>
      /// find tiles around to fill screen
      /// </summary>
      /// <param name="list"></param>
      void FindTilesAround(ref List<Point> list)
      {
         list.Clear();
         for(int i = -sizeOfMapArea.Width; i < sizeOfMapArea.Width; i++)
         {
            for(int j = -sizeOfMapArea.Height; j < sizeOfMapArea.Height; j++)
            {
               Point p = centerTileXYLocation;
               p.X += i;
               p.Y += j;

               if(p.X < sizeOfTiles.Width && p.Y < sizeOfTiles.Height)
               {
                  if(p.X >= 0 && p.Y >= 0)
                  {
                     list.Add(p);
                  }
               }
            }
         }
         Shuffle<Point>(list);
      }

      /// <summary>
      /// tile loader worker
      /// </summary>
      /// <param name="id"></param>
      void LoaderWork(int id)
      {
         bool process = true;
         Point task = new Point();

         lock(tileLoadQueue)
         {
            if(tileLoadQueue.Count > 0)
            {
               task = tileLoadQueue.Dequeue();
            }
            else
            {
               process = false;
            }
         }

         if(process)
         {
            Debug.WriteLine("loader[" + id + "]: download => " + task);

            // report load start
            loader.ReportProgress(id, false);

            Image img = GMapControl.Manager.GetImageFrom(MapType, task, Zoom, Language, UseTileCache);

            Tile x = new Tile(img, new TileCoordinate(task, Zoom), RenderMode);
            matrix[task] = x;

            loader.ReportProgress(id, x.Coordinate);
         }
         else
         {
            Debug.WriteLine("loader[" + id + "]: wait");

            // report load complete
            loader.ReportProgress(id, true);

            waitOnEmptyTasks.WaitOne();  // No more tasks - wait for a signal
         }
      }

      /// <summary>
      /// enqueueens tile to loaad
      /// </summary>
      /// <param name="task"></param>
      void EnqueueLoadTask(Point task)
      {
         lock(tileLoadQueue)
         {
            tileLoadQueue.Enqueue(task);
         }
         waitOnEmptyTasks.Set();
      }

      /// <summary>
      /// bunds checker worker
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      void boundsChecker_DoWork(object sender, DoWorkEventArgs e)
      {
         while(!boundsChecker.CancellationPending)
         {
            waitForBoundsChanged.WaitOne();

            lock(tileDrawingList)
            {
               FindTilesAround(ref tileDrawingList);

               try
               {
                   matrix.ClearPointsNotIn(ref tileDrawingList);
               }
                catch(Exception exx)
                {
                    Console.WriteLine(exx.Message);
                }

               Debug.WriteLine("boundsChecker_DoWork: total tiles => " + tileDrawingList.Count.ToString());

               foreach(Point p in tileDrawingList)
               {
                  if(matrix[p] == null)
                  {
                     Debug.WriteLine("EnqueueLoadTask: " + p.ToString());

                     EnqueueLoadTask(p);
                  }
               }
            }

            GC.Collect(GC.MaxGeneration);
            GC.WaitForPendingFinalizers();

            boundsChecker.ReportProgress(100);
         }
      }

      // loader
      void loader_DoWork(object sender, DoWorkEventArgs e)
      {
         while(!loader.CancellationPending)
         {
            LoaderWork(1);
         }
      }

      // loader
      void loader2_DoWork(object sender, DoWorkEventArgs e)
      {
         while(!loader2.CancellationPending)
         {
            LoaderWork(2);
         }
      }

      // loader
      void loader3_DoWork(object sender, DoWorkEventArgs e)
      {
         while(!loader3.CancellationPending)
         {
            LoaderWork(3);
         }
      }

      /// <summary>
      /// invalidates map on tile loaded
      /// </summary>
      /// <param name="sender"></param>
      /// <param name="e"></param>
      void loader_ProgressChanged(object sender, ProgressChangedEventArgs e)
      {
         if(this.Visible)
         {
            this.Invalidate(false);

            // some tile loader complete
            if(e.UserState != null && e.UserState.GetType() == typeof(bool))
            {
               bool complete = (bool) e.UserState;

               if(complete)
               {
                  if(OnTileLoadComplete != null)
                  {
                     OnTileLoadComplete(e.ProgressPercentage);
                  }
               }
               else
               {
                  if(OnTileLoadStart != null)
                  {
                     OnTileLoadStart(e.ProgressPercentage);
                  }
               }
            }
         }
      }

      /// <summary>
      /// gets rectangle around marker
      /// </summary>
      /// <param name="e"></param>
      /// <param name="m"></param>
      /// <returns></returns>
      Rectangle GetRectForMarker(Point e, Marker m)
      {
         Rectangle rc1 = new Rectangle();
         rc1.Location = new Point(e.X - renderOffset.X, e.Y - renderOffset.Y);
         if(m.Type == MarkerType.Small)
         {
            rc1.Size = new Size(8, 10);
            rc1.Offset(-rc1.Width/2, 10);
         }
         else if(m.Type == MarkerType.Medium)
         {
            rc1.Size = new Size(18, 22);
            rc1.Offset(-rc1.Width/2, 10);
         }
         else // custom
         {
            rc1.Size = m.CustomMarker.Size;

            switch(m.CustomMarkerAlign)
            {
               case CustomMarkerAlign.MiddleMiddle:
               {
                  rc1.Offset(-rc1.Width/2, -rc1.Height/2);
               }
               break;

               case CustomMarkerAlign.Manual:
               {
                  rc1.Offset(-rc1.Width+m.CustomMarkerCenter.X, -rc1.Height+m.CustomMarkerCenter.Y);
               }
               break;
            }
         }
         return rc1;
      }

      ///// <summary>
      ///// checks if some marker was clicked
      ///// </summary>
      ///// <param name="e"></param>
      //void CheckIfClickOnMarker(Point e)
      //{
      //   lock(markers)
      //   {
      //       List<Marker> mm = new List<Marker>();
      //      for(int i = 0; i < markers.Count; i++)
      //      {
      //         Marker m = markers[i];
      //         if (m.Visible && m.tObject.tType != 1010)
      //         {
      //             Rectangle rc1 = GetRectForMarker(e, m);

      //             using (Region rg = new Region(rc1))
      //             {
      //                 if (rg.IsVisible(Manager.FromLatLngToPixel(m.Position, Zoom)))
      //                 {
                           
      //                     mm.Add(m);

      //                 }
      //             }
      //         }
      //         else
      //         {
      //             Rectangle rc1 = GetRectForMarker(e, m);

      //             using (Region rg = new Region(rc1))
      //             {
      //                 if (rg.IsVisible(Manager.FromLatLngToPixel(m.Position, Zoom)))
      //                 {

      //                     if (m.AlwaysVisible)
      //                     {
      //                         if(!m.IsEditMode)
      //                         {
      //                             if (MessageBox.Show("Edit location marker?", "Edit Location?", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
      //                             {
      //                                 m.IsEditMode = !m.IsEditMode;
      //                             }
      //                         }
      //                         else
      //                         {
      //                             if (m.IsEditMode)
      //                             {
      //                                 m.IsEditMode = !m.IsEditMode;
      //                             }
      //                         }
      //                         if (!m.IsEditMode)
      //                         {
      //                             if (OnMakerPositionUpdated != null)
      //                             {
      //                                 OnMakerPositionUpdated(m);
      //                             }
      //                         }
      //                     }

      //                 }
      //             }
                   
      //         }
      //      }//end for

      //      if (OnMarkerClick != null && mm.Count>0)
      //      {
      //          if (!mouseVisible)
      //          {
      //              this.Cursor = System.Windows.Forms.Cursors.Default;
      //              Cursor.Show();
      //              mouseVisible = true;
      //          }
      //          Invalidate(false);
      //          OnMarkerClick(mm);
      //          //break;
      //      }
      //   }
      //}

      /// <summary>
      /// drag map
      /// </summary>
      /// <param name="pt"></param>
      void Drag(Point pt)
      {
         renderOffset.X = pt.X - dragPoint.X;
         renderOffset.Y = pt.Y - dragPoint.Y;

         centerTileXYLocation.X = ((renderOffset.X)/-GoogleMaps.TileXY.Width) + 2;
         centerTileXYLocation.Y = ((renderOffset.Y)/-GoogleMaps.TileXY.Height) + 2;

         if(centerTileXYLocation != centerTileXYLocationLast)
         {
            centerTileXYOffset.X = CurrentPositionGTile.X - centerTileXYLocation.X;
            centerTileXYOffset.Y = CurrentPositionGTile.Y - centerTileXYLocation.Y;
            centerTileXYLocationLast = centerTileXYLocation;

            waitForBoundsChanged.Set();
         }

         SafeInvalidate();
      }

      /// <summary>
      /// initiates map dragging
      /// </summary>
      /// <param name="pt"></param>
      void BeginDrag(Point pt)
      {
         dragPoint.X = pt.X - renderOffset.X;
         dragPoint.Y = pt.Y - renderOffset.Y;
         dragging = true;
      }

      /// <summary>
      /// ends map dragging
      /// </summary>
      void EndDrag()
      {
         dragging = false;
         Invalidate(false);
      }

      /// <summary>
      /// render map in GDI
      /// </summary>
      /// <param name="g"></param>
      void DrawMapGDI(Graphics g)
      {
         try
         {
            hdcTmp = g.GetHdc();
            hdcMemTmp = NativeMethods.CreateCompatibleDC(hdcTmp);

            for(int i = -(sizeOfMapArea.Width + centerTileXYOffset.X); i < (sizeOfMapArea.Width - centerTileXYOffset.X); i++)
            {
               for(int j = -(sizeOfMapArea.Height + centerTileXYOffset.Y); j < (sizeOfMapArea.Height - centerTileXYOffset.Y); j++)
               {
                  tilePoint = CurrentPositionGTile;
                  tilePoint.X += i;
                  tilePoint.Y += j;

                  Tile t = matrix[tilePoint];
                  if(t != null) // debug center tile add: && p != centerTileXYLocation
                  {
                     if(t.Hbitmap != null)
                     {
                        tileRect.Location = new Point(tilePoint.X*tileRect.Width, tilePoint.Y*tileRect.Height);
                        tileRect.Offset(renderOffset);

                        if(this.Region.IsVisible(tileRect))
                        {
                           // Select our bitmap in to DC, recording what was there before
                           IntPtr oldObject = NativeMethods.SelectObject(hdcMemTmp, t.Hbitmap);

                           // Perform blt
                           NativeMethods.BitBlt(hdcTmp, tileRect.X, tileRect.Y, tileRect.Width, tileRect.Height, hdcMemTmp, 0, 0, SourceCopy);

                           // Select our bitmap object back out of the DC
                           NativeMethods.SelectObject(hdcMemTmp, oldObject);
                        }
                     }
                  }
               }
            }
         }
         finally
         {
            NativeMethods.DeleteDC(hdcMemTmp);
            g.ReleaseHdc(hdcTmp);
         }
      }

      /// <summary>
      /// render map in GDI+
      /// </summary>
      /// <param name="g"></param>
      void DrawMapGDIplus(Graphics g)
      {
          
         for(int i = -(sizeOfMapArea.Width + centerTileXYOffset.X); i < (sizeOfMapArea.Width - centerTileXYOffset.X); i++)
         {
            for(int j = -(sizeOfMapArea.Height + centerTileXYOffset.Y); j < (sizeOfMapArea.Height - centerTileXYOffset.Y); j++)
            {
               tilePoint = CurrentPositionGTile;
               tilePoint.X += i;
               tilePoint.Y += j;

               Tile t = matrix[tilePoint];
               if(t != null) // debug center tile add: && p != centerTileXYLocation
               {
                  if(t.Image != null)
                  {
                     tileRect.Location = new Point(tilePoint.X*tileRect.Width, tilePoint.Y*tileRect.Height);
                     tileRect.Offset(renderOffset);

                     if(this.Region.IsVisible(tileRect))
                     {
                        g.DrawImageUnscaled(t.Image, tileRect.Location);
                     }
                  }
               }
            }
         }
         
      }

      /// <summary>
      /// draw routes
      /// </summary>
      /// <param name="g"></param>
      void DrawRoutes(Graphics g)
      {
         GraphicsState st = g.Save();
         g.SmoothingMode = SmoothingMode.AntiAlias;

         lock(routes)
         {
            foreach(Route r in routes)
            {
               routePen.Color = r.Color;
               Point p1 = Point.Empty;

               for(int i = 0; i < r.Points.Count; i++)
               {
                  Point p2 = Manager.FromLatLngToPixel(r.Points[i], Zoom);
                  p2.Offset(renderOffset);

                  if(i == 0)
                  {
                     p1 = p2;
                  }

                  {
                     g.DrawLine(routePen, p1, p2);

                     //RectangleF rect = new RectangleF(p2.X-1F, p2.Y-1F, 2F, 2F);
                     //g.DrawEllipse(routePen, rect);
                     //g.FillEllipse(Brushes.MidnightBlue, rect);
                  }

                  p1 = p2;
               }
            }
         }

         g.Restore(st);
      }

      void DrawRoutes(Graphics g, Marker mk)
      {
          GraphicsState st = g.Save();
          g.SmoothingMode = SmoothingMode.AntiAlias;

             
                  routePen.Color = mk.Routes.Color;
                  Point p1 = Point.Empty;

                  for (int i = 0; i < mk.Routes.Points.Count; i++)
                  {
                      Point p2 = Manager.FromLatLngToPixel(mk.Routes.Points[i], Zoom);
                      p2.Offset(renderOffset);

                      if (i == 0)
                      {
                          p1 = p2;
                      }

                      {
                          g.DrawLine(routePen, p1, p2);

                          //RectangleF rect = new RectangleF(p2.X-1F, p2.Y-1F, 2F, 2F);
                          //g.DrawEllipse(routePen, rect);
                          //g.FillEllipse(Brushes.MidnightBlue, rect);
                      }

                      p1 = p2;
                  }
              
          

          g.Restore(st);
      }

      void DrawMarkersTAV(Graphics g)
      {
          List<Marker> m2Draw = new List<Marker>();
          foreach (Marker m in markers)
          {
              Point p = Manager.FromLatLngToPixel(m.Position, Zoom);
              if (m.Visible && this.Region.IsVisible(p))
              {
                  m2Draw.Add(m);
              }
          }
      }

      /// <summary>
      /// draw markers
      /// </summary>
      /// <param name="g"></param>
      void DrawMarkers(Graphics g)
      {
         lock(markers)
         {
             int cnt = 1;
            foreach(Marker m in markers)
            {
               Point p = Manager.FromLatLngToPixel(m.Position, Zoom);
               p.Offset(renderOffset);

               if((m.Visible && this.Region.IsVisible(p)) || (m.AlwaysVisible&&this.Region.IsVisible(p)) || m.DrawRoute)
               {
                   if (!m.AlwaysVisible)
                   {
                       if (m.Routes.Points.Count > 0 && m.DrawRoute)
                       {
                           DrawRoutes(g, m);
                       }
                   }
                   else
                   {
                       if (m.IsEditMode)
                       {
                           Point pp = PointToClient(new Point(MousePosition.X, MousePosition.Y));
                           PointLatLng pll = Manager.FromPixelToLatLng(new Point(pp.X - renderOffset.X, pp.Y - renderOffset.Y), zoom);
                           m.Position = pll;
                           g.DrawRectangle(Pens.Red, p.X - 20, p.Y - 20, m.CustomMarker.Width / 2 + 15, m.CustomMarker.Height / 2 + 15);
                       }
                   }
                  switch(m.Type)
                  {
                     #region -- medium --
                     case MarkerType.Medium:
                     {
                        // shadow
                        g.DrawImageUnscaled(Resources.shadow50, p.X-10, p.Y-34);

                        #region -- marker --
                        switch(m.Color)
                        {
                           case MarkerColor.Blue:
                           g.DrawImageUnscaled(Resources.blue_dot, p.X-16, p.Y-32);
                           break;

                           case MarkerColor.Green:
                           g.DrawImageUnscaled(Resources.green_dot, p.X-16, p.Y-32);
                           break;

                           case MarkerColor.Yellow:
                           g.DrawImageUnscaled(Resources.yellow_dot, p.X-16, p.Y-32);
                           break;

                           case MarkerColor.Red:
                           g.DrawImageUnscaled(Resources.red_dot, p.X-16, p.Y-32);
                           break;
                        }
                        #endregion

                        switch(m.TooltipMode)
                        {
                           #region -- on mouse over --
                           case MarkerTooltipMode.OnMouseOver:
                           {
                              if(m.IsMouseOver)
                              {
                                  DrawToolTip(g, m, p.X, (p.Y - 24) + cnt, p.Y - 24);
                                 
                              }
                           }
                           break;
                           #endregion

                           #region -- allways --
                           case MarkerTooltipMode.Always:
                           {
                               DrawToolTip(g, m, p.X, (p.Y - 24) + cnt, p.Y - 24);
                              
                           }
                           break;
                           #endregion
                        }
                     }
                     break;
                     #endregion

                     #region -- small --
                     case MarkerType.Small:
                     {
                        // shadow
                        g.DrawImageUnscaled(Resources.mm_20_shadow, p.X-6, p.Y-20);

                        #region -- marker --
                        switch(m.Color)
                        {
                           case MarkerColor.Blue:
                           g.DrawImageUnscaled(Resources.mm_20_blue, p.X-6, p.Y-20);
                           break;

                           case MarkerColor.Green:
                           g.DrawImageUnscaled(Resources.mm_20_green, p.X-6, p.Y-20);
                           break;

                           case MarkerColor.Yellow:
                           g.DrawImageUnscaled(Resources.mm_20_yellow, p.X-6, p.Y-20);
                           break;

                           case MarkerColor.Red:
                           g.DrawImageUnscaled(Resources.mm_20_red, p.X-6, p.Y-20);
                           break;
                        }
                        #endregion

                        #region -- tooltip --
                        switch(m.TooltipMode)
                        {
                           case MarkerTooltipMode.OnMouseOver:
                           {
                              if(m.IsMouseOver)
                              {
                                  DrawToolTip(g, m, p.X, (p.Y - 15) + cnt, p.Y - 15);
                                 
                              }
                           }
                           break;

                           case MarkerTooltipMode.Always:
                           {
                               DrawToolTip(g, m, p.X, (p.Y - 15) + cnt, p.Y - 15);
                              
                           }
                           break;
                        }
                        #endregion
                     }
                     break;
                     #endregion

                     #region -- custom --
                     case MarkerType.Custom:
                     {
                        if(m.CustomMarker != null)
                        {
                           #region -- marker --
                           switch(m.CustomMarkerAlign)
                           {
                              case CustomMarkerAlign.MiddleMiddle:
                              {
                                 g.DrawImageUnscaled(m.CustomMarker, p.X-m.CustomMarker.Width/2, p.Y-m.CustomMarker.Height/2);
                                 //g.DrawImageUnscaled(m.CustomMarker, MousePosition.X - m.CustomMarker.Width / 2, MousePosition.X - m.CustomMarker.Height / 2);
                              }
                              break;

                              case CustomMarkerAlign.Manual:
                              {
                                  
                                 g.DrawImageUnscaled(m.CustomMarker, p.X-m.CustomMarkerCenter.X, p.Y-m.CustomMarkerCenter.Y);
                              }
                              break;
                           }
                           #endregion

                           #region -- tooltip --

                           bool drawooltip = false;
                           switch(m.TooltipMode)
                           {
                              case MarkerTooltipMode.OnMouseOver:
                              {
                                 drawooltip = m.IsMouseOver;
                              }
                              break;

                              case MarkerTooltipMode.Always:
                              {
                                 drawooltip = true;
                              }
                              break;

                              case MarkerTooltipMode.Never:
                              {
                                 drawooltip = false;
                              }
                              break;
                           }

                           if(drawooltip)
                           {
                              switch(m.CustomMarkerAlign)
                              {
                                 case CustomMarkerAlign.MiddleMiddle:
                                 {
                                     DrawToolTip(g, m, p.X, (p.Y) + cnt, p.Y);
                                    cnt += 30;
                                 }
                                 break;

                                 case CustomMarkerAlign.Manual:
                                 {
                                     DrawToolTip(g, m, p.X, (p.Y) + cnt, p.Y);
                                   cnt += 30;
                                 }
                                 break;
                              }
                           }
                           #endregion
                        }
                     }
                     break;
                     #endregion
                  }
                  
               }
               
            }
         }
      }

      /// <summary>
      /// draw current marker
      /// </summary>
      /// <param name="g"></param>
      void DrawCurrentMarker(Graphics g)
      {
         // current marker
         {
            Point p = CurrentPositionGPixel;
            p.Offset(renderOffset);
            {
               switch(CurrentMarkerStyle)
               {
                  case CurrentMarkerType.GMap:
                  {
                     if(!IsDragging)
                     {
                        g.DrawImageUnscaled(Resources.shadow50, p.X-10, p.Y-34);
                        g.DrawImageUnscaled(Resources.marker, p.X-10, p.Y-34);
                     }
                     else
                     {
                        g.DrawImageUnscaled(Resources.shadow50, p.X-10, p.Y-40);
                        g.DrawImageUnscaled(Resources.marker, p.X-10, p.Y-40);
                        g.DrawImageUnscaled(Resources.drag_cross_67_16, p.X-8, p.Y-8);
                     }
                  }
                  break;

                  case CurrentMarkerType.Cross:
                  {
                     Point p1 = p;
                     p1.Offset(0, -10);
                     Point p2 = p;
                     p2.Offset(0, 10);

                     Point p3 = p;
                     p3.Offset(-10, 0);
                     Point p4 = p;
                     p4.Offset(10, 0);

                     g.DrawLine(Pens.Red, p1, p2);
                     g.DrawLine(Pens.Red, p3, p4);
                  }
                  break;

                  case CurrentMarkerType.Custom:
                  {
                     DrawCurrentMarker(g, p.X, p.Y);
                  }
                  break;
               }
            }
         }
      }

      /// <summary>
      /// gets max zoom level to fit rectangle
      /// </summary>
      /// <param name="rect"></param>
      /// <returns></returns>
      internal int GetMaxZoomToFitRect(RectLatLng rect)
      {
         int zoom = 0;  

         for(int i = 1; i <= Manager.MaxZoom; i++)
         {
            Point p1 = Manager.FromLatLngToPixel(rect.Location, i); 
            Point p2 = Manager.FromLatLngToPixel(rect.Bottom, rect.Right, i);

            if(((p2.X - p1.X) <= Width+10) && (p2.Y - p1.Y) <= Height+10)
            {
               zoom = i;
            }
            else
            {
               break;
            }
         }

         return zoom;
      }

      #endregion

      #region -- protected --

      /// <summary>
      /// draws tooltip, override to draw custom
      /// </summary>
      /// <param name="g"></param>
      /// <param name="pos"></param>
      public virtual int DrawToolTip(Graphics g, Marker m, int x, int y, int oy)
      {
         GraphicsState s = g.Save();
         g.SmoothingMode = SmoothingMode.AntiAlias;



         Size st = g.MeasureString(m.Text, TooltipFont).ToSize();
         Rectangle rect = new Rectangle(x, y, st.Width+TooltipTextPadding.Width, st.Height+TooltipTextPadding.Height);
         rect.Offset(m.ToolTipOffset);
         tooltipPen.Color = m.borderColor;

         g.DrawLine(tooltipPen, x, oy, rect.X /*+ rect.Width/2*/, rect.Y + rect.Height/2);
         g.FillRectangle(new SolidBrush(tooltipBg), rect);
         g.DrawRectangle(tooltipPen, rect);
         g.DrawString(m.Text, TooltipFont, Brushes.White, rect, tooltipFormat);

         g.Restore(s);
         return y;
      }

      /// <summary>
      /// override to draws custom current marker
      /// </summary>
      /// <param name="g"></param>
      /// <param name="x"></param>
      /// <param name="y"></param>
      protected virtual void DrawCurrentMarker(Graphics g, int x, int y)
      {

      }

      protected override void OnPaintBackground(PaintEventArgs e)
      {
         // ...
      }

      protected override void OnSizeChanged(EventArgs e)
      {
         sizeOfMapArea = Bounds.Size;
         sizeOfMapArea.Height /= GoogleMaps.TileXY.Height;
         sizeOfMapArea.Width /= GoogleMaps.TileXY.Width;
         sizeOfMapArea.Height += 1;
         sizeOfMapArea.Width += 1;

         sizeOfMapArea.Width = sizeOfMapArea.Width/2 + 2;
         sizeOfMapArea.Height = sizeOfMapArea.Height/2 + 2;

         // 50px outside control
         this.Region = new Region(new Rectangle(-50, -50, Size.Width+100, Size.Height+100));

         base.OnSizeChanged(e);
      }

      protected override void OnPaint(PaintEventArgs e)
      {
         if(DesignMode)
            return;

         switch(RenderMode)
         {
            case RenderMode.GDI:
            {
               try
               {
                  DrawMapGDI(e.Graphics);
               }
               catch // drop to gdi+ redering if smth
               {
                  RenderMode = RenderMode.GDI_PLUS;
               }
            }
            break;

            case RenderMode.GDI_PLUS:
            {
               DrawMapGDIplus(e.Graphics);
            }
            break;

            
         }

         #region -- debug --
         /*
         //e.Graphics.DrawString("Area : " + area.ToString(), txtFont, Brushes.Red, 10F, 10F);
         e.Graphics.DrawString("Offset : " + renderOffset.ToString(), txtFont, Brushes.Red, 10F, 35F);
       
         //e.Graphics.DrawString("MouseDown: " + mouseDown.ToString(), txtFont, Brushes.Red, 10F, 95F);
         //e.Graphics.DrawString("MouseCurr: " + mouseCurrent.ToString(), txtFont, Brushes.Red, 10F, 125F);


         //e.Graphics.DrawString("DragPoint: " + dragPoint.ToString(), txtFont, Brushes.Red, 10F, 165F);
         //e.Graphics.DrawString("MarkPoint: " + markerCurrentCoord.ToString(), txtFont, Brushes.Red, 10F, 125F);
         //e.Graphics.DrawString("MarkCoord: " + markCoord.ToString(), txtFont, Brushes.Red, 10F, 145F);

         e.Graphics.DrawString("CenterTile: " + centerTileXYLocation.ToString(), txtFont, Brushes.Red, 10F, 185F);
         e.Graphics.DrawString("OffsetTile: " + centerTileXYOffset.ToString(), txtFont, Brushes.Red, 10F, 205F);
         
         e.Graphics.DrawString("CurrGPixel: " + CurrentPositionGPixel.ToString(), txtFont, Brushes.Red, 10F, 235F);
         e.Graphics.DrawString("CurrGTile : " + CurrentPositionGTile.ToString(), txtFont, Brushes.Red, 10F, 255F);
         */
         #endregion

         if(RoutesEnabled && (!IsDragging || Form.ModifierKeys == Keys.Control))
         {
            DrawRoutes(e.Graphics);
         }

         if(MarkersEnabled && (!IsDragging || Form.ModifierKeys == Keys.Control))
         {
            DrawMarkers(e.Graphics);
         }

         if(CurrentMarkerEnabled)
         {
            DrawCurrentMarker(e.Graphics);
         }

         #region -- copyright --
         if(copyrightSizeF == SizeF.Empty)
         {
            copyrightSizeF = e.Graphics.MeasureString(copyright, gFont);
         }
         e.Graphics.DrawString(copyright, gFont, Brushes.Navy, Width - copyrightSizeF.Width + 3, Height - copyrightSizeF.Height - 5);
         #endregion
         //e.Graphics.DrawString(mouseLatLon, new Font("Ariel", 12), Brushes.Black, new PointF(20, 10));

         if (OnPaintComplete != null)
         {
             OnPaintComplete(e.Graphics);
         }
      }

      protected override void OnResize(EventArgs e)
      {
         base.OnResize(e);

         if(this.Visible)
         {
            this.Invalidate(false);
         }
      }

      public void changePosition(Point p)
      {
          ChangeCurrentPositionOnly(p);
      }
      protected override void OnMouseDown(MouseEventArgs e)
      {
         mouseDown = e.Location;

         if(e.Button == MouseButtons.Left)
         {
             if (CurrentMarkerEnabled && !IsMouseOverMarker)
             {
                 ChangeCurrentPositionOnly(new Point(e.X - renderOffset.X, e.Y - renderOffset.Y));

                 if (mouseVisible)
                 {
                     this.Cursor = System.Windows.Forms.Cursors.Default;
                     Cursor.Hide();
                     mouseVisible = false;
                 }

                 BeginDrag(mouseDown);
             }
             else
             {
                 if (!IsMouseOverMarker)
                 {
                     if (CanDragMap)
                     {
                         this.Cursor = System.Windows.Forms.Cursors.SizeAll;

                         BeginDrag(mouseDown);
                     }
                 }
             }
         }
         else if(e.Button == MouseButtons.Right)
         {
            if(CanDragMap)
            {
               this.Cursor = System.Windows.Forms.Cursors.SizeAll;

               BeginDrag(mouseDown);
            }
         }

         this.Invalidate(false);

         base.OnMouseDown(e);
      }

      protected override void OnMouseUp(MouseEventArgs e)
      {
         if(IsDragging)
         {
            EndDrag();

            this.Cursor = System.Windows.Forms.Cursors.Default;

            if(!mouseVisible)
            {
               Cursor.Show();
               mouseVisible = true;
            }
         }

         base.OnMouseUp(e);
      }

      protected override void OnMouseClick(MouseEventArgs e)
      {
         if(e.Button == MouseButtons.Left)
         {
             try
             {
                 //CheckIfClickOnMarker(e.Location);
             }
             catch (Exception ex)
             {
                 Console.WriteLine(ex.Message);
             }
         }

         base.OnMouseClick(e);
      }

      string mouseLatLon = "";
      protected override void OnMouseMove(MouseEventArgs e)
      {
          PointLatLng pll = Manager.FromPixelToLatLng(e.Location, zoom);
          mouseLatLon = "Lat: " + pll.Lat.ToString() + " Lon: " + pll.Lng.ToString();
         if(IsDragging)
         {
            mouseCurrent = e.Location;

            if(e.Button == MouseButtons.Right)
            {
               Drag(mouseCurrent);
            }
            else if(e.Button == MouseButtons.Left)
            {
                if (CurrentMarkerEnabled)
                {
                    ChangeCurrentPositionOnly(new Point(e.X - renderOffset.X, e.Y - renderOffset.Y));
                    Invalidate(false);
                }
                else
                {
                    Drag(mouseCurrent);
                }
            }
         }
         else
         {
            lock(markers)
            {
               for(int i = 0; i < markers.Count; i++)
               {
                  Marker m = markers[i];
                  if(m.Visible)
                  {
                     Rectangle rc1 = GetRectForMarker(e.Location, m);

                     using(Region rg = new Region(rc1))
                     {
                        if(rg.IsVisible(Manager.FromLatLngToPixel(m.Position, Zoom)))
                        {
                           this.Cursor = System.Windows.Forms.Cursors.Hand;
                           m.IsMouseOver = true;
                           isMouseOverMarker = true;
                           Invalidate(false);

                           if(OnMarkerEnter != null)
                           {
                              OnMarkerEnter(m);
                           }
                        }
                        else if(m.IsMouseOver)
                        {
                           this.Cursor = System.Windows.Forms.Cursors.Default;
                           m.IsMouseOver = false;
                           isMouseOverMarker = false;
                           Invalidate(false);

                           if(OnMarkerLeave != null)
                           {
                              OnMarkerLeave(m);
                           }
                        }
                     }
                  }
               }
            }
         }
         base.OnMouseMove(e);
      }
      #endregion

      #region -- static --
      static internal void Shuffle<T>(IList<T> deck)
      {
         Random random = new System.Random();
         int N = deck.Count;

         for(int i = 0; i < N; ++i)
         {
            int r = i + (int) (random.Next(N - i));
            T t = deck[r];
            deck[r] = deck[i];
            deck[i] = t;
         }
      }
      #endregion

      internal PointLatLng FromPixelToLatLng(Point point)
      {
          return Manager.FromPixelToLatLng(point, zoom);
      }
   }
}
